/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2003 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: storechg.c,v 1.4 2005/07/12 23:12:29 pullmoll Exp $
 *****************************************************************************/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>

#ifndef	MAXPATHLEN
#define	MAXPATHLEN	256
#endif

#define	CONFIGFILE	"entropy.conf"
#define	STOREPATH	"store"

#define	SHA1SIZE	20
#define	TIMETSIZE	4
#define	CHUNKSIZE	(256*1024)
#define	INDEXSIZE	(SHA1SIZE+TIMETSIZE)

/* This is really file system dependent; we use an approximation */
#define	BLOCKSIZE	512

typedef struct file_s {
	char src[MAXPATHLEN];
	char dst[MAXPATHLEN];
}	file_t;

const char rotor[4] = {'|', '/', '-', '\\'};
int delete = 0;

int sort_src(const void *p1, const void *p2)
{
	const file_t *f1 = (const file_t *)p1;
	const file_t *f2 = (const file_t *)p2;
	return strcmp(f1->src, f2->src);
}

int sort_dst(const void *p1, const void *p2)
{
	const file_t *f1 = (const file_t *)p1;
	const file_t *f2 = (const file_t *)p2;
	return strcmp(f1->dst, f2->dst);
}

int move_tree_0_1(const char *path, int digit)
{
	file_t *file = NULL;
	file_t *fe;
	size_t file_cnt = 0;
	size_t file_max = 0;
	size_t count;
	char dirname[MAXPATHLEN];
	DIR *dir;
	struct dirent *de;
	int idx;

	dir = opendir(path);
	if (NULL == dir) {
		fprintf(stderr, "cannot read directory '%s' (%s)\n",
			path, strerror(errno));
		return -1;
	}
	printf("moving down %s ", path);
	fflush(stdout);
	/* make subdirectories */
	for (idx = 0; idx < 16; idx++) {
		sprintf(dirname, "%s/%x",
			path, idx);
		mkdir(dirname, 0700);
	}
	count = 0;
	while (NULL != (de = readdir(dir))) {
		if (2*SHA1SIZE != strlen(de->d_name))
			continue;
		if (file_cnt >= file_max) {
			file_max = (0 == file_max) ? 1024 : file_max * 2;
			file = realloc(file, file_max * sizeof(file_t));
			if (NULL == file) {
				fprintf(stderr, "realloc(file, %#x) failed (%s)\n",
					(unsigned)(file_max * sizeof(file_t)), strerror(errno));
				return -1;
			}
			memset(&file[file_cnt], 0,
				(file_max - file_cnt) * sizeof(file_t));
		}
		fe = &file[file_cnt++];
		sprintf(fe->src, "%s/%s",
			path, de->d_name);
		sprintf(fe->dst, "%s/%c/%s",
			path, de->d_name[digit], de->d_name);
		count++;
		if (0 == (count % 64)) {
			printf("%c\b", rotor[(count/64) % 4]);
			fflush(stdout);
		}
	}
	closedir(dir);
	printf("%d files ", (int)file_cnt);
	if (file_cnt > 0) {
		qsort(file, file_cnt, sizeof(file_t), sort_src);
		for (count = 0; count < file_cnt; count++) {
			fe = &file[count];
			if (0 != rename(fe->src, fe->dst)) {
				fprintf(stderr, "move %s -> %s failed\n",
					fe->src, fe->dst);
			}
			if (0 == (count % 64)) {
				printf("%3d%%\b\b\b\b", (int)(100 * count / file_cnt));
				fflush(stdout);
			}
		}
	}
	printf("100%%\n");
	free(file);
	return 0;
}

int move_tree_1_2(const char *path, int digit)
{
	char src[MAXPATHLEN];
	int idx, rc;

	for (idx = 0; idx < 16; idx++) {
		sprintf(src, "%s/%x", path, idx);
		if (0 != (rc = move_tree_0_1(src, digit + 1))) {
			return rc;
		}
	}

	return rc;
}

int move_tree_2_3(const char *path, int digit)
{
	char src[MAXPATHLEN];
	int idx, rc;

	for (idx = 0; idx < 16; idx++) {
		sprintf(src, "%s/%x", path, idx);
		if (0 != (rc = move_tree_1_2(src, digit + 1))) {
			return rc;
		}
	}

	return rc;
}

int move_tree_1_0(const char *path)
{
	file_t *file = NULL;
	file_t *fe;
	size_t file_cnt = 0;
	size_t file_max = 0;
	size_t count;
	char dirname[MAXPATHLEN];
	DIR *dir;
	struct dirent *de;
	int idx;

	printf("moving up to %s ", path);
	fflush(stdout);
	count = 0;
	/* moving up from subdirectories */
	for (idx = 0; idx < 16; idx++) {
		sprintf(dirname, "%s/%x",
			path, idx);
		dir = opendir(dirname);
		if (NULL == dir) {
			/* silently skip empty (or inaccessible) sub directories */
			continue;
		}
		while (NULL != (de = readdir(dir))) {
			if (2*SHA1SIZE != strlen(de->d_name))
				continue;
			if (file_cnt >= file_max) {
				file_max = (0 == file_max) ? 1024 : file_max * 2;
				file = realloc(file, file_max * sizeof(file_t));
				if (NULL == file) {
					fprintf(stderr, "realloc(file, %#x) failed (%s)\n",
						(unsigned)(file_max * sizeof(file_t)), strerror(errno));
					return -1;
				}
				memset(&file[file_cnt], 0,
					(file_max - file_cnt) * sizeof(file_t));
			}
			fe = &file[file_cnt++];
			sprintf(fe->src, "%s/%s",
				dirname, de->d_name);
			sprintf(fe->dst, "%s/%s",
				path, de->d_name);
			count++;
			if (0 == (count % 64)) {
				printf("%c\b", rotor[(count/64) % 4]);
				fflush(stdout);
			}
		}
		closedir(dir);
	}
	printf("%d files ", (int)file_cnt);
	if (file_cnt > 0) {
		qsort(file, file_cnt, sizeof(file_t), sort_dst);
		for (count = 0; count < file_cnt; count++) {
			fe = &file[count];
			if (0 != rename(fe->src, fe->dst)) {
				fprintf(stderr, "move %s -> %s failed\n",
					fe->src, fe->dst);
			}
			if (0 == (count % 64)) {
				printf("%3d%%\b\b\b\b", (int)(100 * count / file_cnt));
				fflush(stdout);
			}
		}
	}
	printf("100%%\n");
	free(file);
	/* remove the subdirectores ... if they are empty */
	for (idx = 0; idx < 16; idx++) {
		sprintf(dirname, "%s/%x",
			path, idx);
		rmdir(dirname);
	}
	return 0;
}

int move_tree_2_1(const char *path)
{
	char dst[MAXPATHLEN];
	int idx, rc;

	for (idx = 0; idx < 16; idx++) {
		sprintf(dst, "%s/%x", path, idx);
		if (0 != (rc = move_tree_1_0(dst))) {
			return rc;
		}
	}

	return rc;
}

int move_tree_3_2(const char *path)
{
	char dst[MAXPATHLEN];
	int idx, rc;

	for (idx = 0; idx < 16; idx++) {
		sprintf(dst, "%s/%x", path, idx);
		if (0 != (rc = move_tree_2_1(dst))) {
			return rc;
		}
	}

	return rc;
}

char *hexstr(uint8_t *src, size_t size)
{
	static char hexbuff[256];
	char *dst = hexbuff;
	size_t i;

	if (size >= sizeof(hexbuff)/2)
		size = sizeof(hexbuff)/2 - 1;

	for (i = 0; i < size; i++)
		dst += sprintf(dst, "%02x", src[i]);
	*dst = '\0';

	return hexbuff;
}

int move_tree_x(const char *path, const char *src, size_t keys, int depth)
{
	char dst[MAXPATHLEN];
	FILE *fi, *fo;
	uint8_t *key, *k, *c, null[20], p1, p2, p3;
	size_t i, size, count, total;
	struct stat st;
	struct timeval times[2];

	memset(null, 0, sizeof(null));
	memset(times, 0, sizeof(times));

	if (0 != stat(src, &st)) {
		fprintf(stderr, "\nstat(%s) failed (%s)\n",
			src, strerror(errno));
		return -1;
	}

	/* set mtime of all the keys to ctime of the mono file */
	times[1].tv_sec = st.st_ctime;

	printf("\rextracting from %s ", src);
	fflush(stdout);

	c = malloc(CHUNKSIZE);
	if (NULL == c) {
		fprintf(stderr, "\nmalloc(%d) failed (%s)\n",
			CHUNKSIZE, strerror(errno));
		return -1;
	}

	size = keys * INDEXSIZE;
	key = k = malloc(size);
	if (NULL == key) {
		fprintf(stderr, "\nmalloc(%d) failed (%s)\n",
			(int)size, strerror(errno));
		free(c);
		return -1;
	}

	fi = fopen(src, "rb");
	if (NULL == fi) {
		fprintf(stderr, "\nfopen(%s) failed (%s)\n",
			src, strerror(errno));
		free(c);
		free(k);
		return -1;
	}

	if (size != fread(k, 1, size, fi)) {
		fprintf(stderr, "\nfread(%s, 0x%x) failed (%s)\n",
			src, (unsigned)size, strerror(errno));
		free(c);
		free(k);
		return -1;
	}

	count = 0;
	total = 0;
	for (i = 0; i < keys; i++, key += INDEXSIZE) {
		if (0 == memcmp(key, null, SHA1SIZE)) {
			/* skip this chunk */
			fseek(fi, CHUNKSIZE, SEEK_CUR);
		} else {
			switch (depth) {
			case 0:
				sprintf(dst, "%s/%s",
					path,
					hexstr(key, 20));
				break;
			case 1:
				p1 = key[SHA1SIZE - 2] / 16;
				sprintf(dst, "%s/%x/%s",
					path, p1,
					hexstr(key, 20));
				break;
			case 2:
				p1 = key[SHA1SIZE - 2] / 16;
				p2 = key[SHA1SIZE - 2] % 16;
				sprintf(dst, "%s/%x/%x/%s",
					path, p1, p2,
					hexstr(key, 20));
				break;
			case 3:
				p1 = key[SHA1SIZE - 2] / 16;
				p2 = key[SHA1SIZE - 2] % 16;
				p3 = key[SHA1SIZE - 1] / 16;
				sprintf(dst, "%s/%x/%x/%x/%s",
					path, p1, p2, p3,
					hexstr(key, 20));
				break;
			}
			if (CHUNKSIZE != fread(c, 1, CHUNKSIZE, fi)) {
				fprintf(stderr, "\nfread(%s, 0x%x) failed (%s)\n",
					src, CHUNKSIZE, strerror(errno));
				perror("fread");
				free(c);
				free(k);
				return -1;
			}
			size = CHUNKSIZE - 1;
			while (size > 0 && 0x00 == c[size])
				size--;
			size++;
			fo = fopen(dst, "wb");
			if (NULL == fo) {
				fprintf(stderr, "\ncreating %s failed (%s)\n",
					dst, strerror(errno));
				free(c);
				free(k);
				return -1;
			}
			if (size != fwrite(c, 1, size, fo)) {
				fprintf(stderr, "\nfwrite(%s, 0x%x) failed (%s)\n",
					dst, (unsigned)size, strerror(errno));
				free(c);
				free(k);
				return -1;
			}
			fclose(fo);
			times[0].tv_sec = *(time_t *)&key[SHA1SIZE];
			if (0 != utimes(dst, times)) {
				fprintf(stderr, "\nutimes(%s, 0x%x) failed (%s)\n",
					dst, (unsigned)times[0].tv_sec, strerror(errno));
				free(c);
				free(k);
				return -1;
			}
			count++;
			if (0 == (count % 64)) {
				printf("%c\b", rotor[(count/64) % 4]);
				fflush(stdout);
			}
			total += (size + BLOCKSIZE - 1) & ~(BLOCKSIZE - 1);
		}
	}
	free(c);
	free(k);
	fclose(fi);
	printf("%d files, %d.%dM\n", (int)count,
		(int)(total / 1024 / 1024),
		(int)((total / 1024) % 1024 * 10 / 1024));
	return 0;
}

int move_tree_m_x(const char *path, int depth)
{
	char src[MAXPATHLEN];
	int idx, rc = 0;
	struct stat st;
	size_t keys;

	for (idx = 0; idx < 256; idx++) {
		sprintf(src, "%s/store.%02x", path, idx);
		if (0 == stat(src, &st)) {
			keys = st.st_size / (CHUNKSIZE + INDEXSIZE);
			rc = move_tree_x(path, src, keys, depth);
			if (0 != rc)
				break;
			if (0 != delete) {
				rc = unlink(src);
				if (0 != rc) {
					fprintf(stderr, "unlinking %s failed (%s)\n",
						src, strerror(errno));
				}
			}
		}
	}
	return rc;
}

int move_tree_m_0(const char *path)
{
	return move_tree_m_x(path, 0);
}

int move_tree_m_1(const char *path)
{
	char dir[MAXPATHLEN];
	size_t i;

	for (i = 0; i < 16; i++) {
		sprintf(dir, "%s/%x", path, (int)i);
		mkdir(dir, 0700);
	}
	return move_tree_m_x(path, 1);
}

int move_tree_m_2(const char *path)
{
	char dir[MAXPATHLEN];
	size_t i, j;

	for (i = 0; i < 16; i++) {
		sprintf(dir, "%s/%x", path, (int)i);
		mkdir(dir, 0700);
		for (j = 0; j < 16; j++) {
			sprintf(dir, "%s/%x/%x", path, (int)i, (int)j);
			mkdir(dir, 0700);
		}
	}
	return move_tree_m_x(path, 2);
}

int move_tree_m_3(const char *path)
{
	char dir[MAXPATHLEN];
	size_t i, j, k;

	for (i = 0; i < 16; i++) {
		sprintf(dir, "%s/%x", path, (int)i);
		mkdir(dir, 0700);
		for (j = 0; j < 16; j++) {
			sprintf(dir, "%s/%x/%x", path, (int)i, (int)j);
			mkdir(dir, 0700);
			for (k = 0; k < 16; k++) {
				sprintf(dir, "%s/%x/%x/%x", path, (int)i, (int)j, (int)k);
				mkdir(dir, 0700);
			}
		}
	}
	return move_tree_m_x(path, 3);
}

int main(int argc, char **argv)
{
	char store[MAXPATHLEN];
	int old_depth;
	int new_depth;
	int i;

	if (argc < 3) {
		fprintf(stderr, "usage: %s [options] <old> <new>\n",
			argv[0]);
		fprintf(stderr, "Options can be one or more of:\n");
		fprintf(stderr, "-s[tore] path\tspecify different store path (./store)\n");
		fprintf(stderr, "-d[delete]\tremove monolithic store files after extraction\n");
		fprintf(stderr, "The mandatory <old> and <new> can be:\n");
		fprintf(stderr, "Two numbers, where old is new+1 (increasing depth) or\n");
		fprintf(stderr, "old+1 is new (decreasing) depth. If you currently have storedepth=1\n");
		fprintf(stderr, "and want to have 2, call './storechg 1 2'.\n");
		fprintf(stderr, "Alternatively <old> can be 'mono' (without the quotes) and <new>\n");
		fprintf(stderr, "a number for the new storedept= value of the tree.\n");
		exit(42);
	}

	delete = 0;
	strcpy(store, STOREPATH);
	i = 1;
	while (i < argc && argv[i][0] == '-') {
		if (0 == strncmp(argv[i], "-s", 2)) {
			if (i + 1 >= argc) {
				fprintf(stderr, "Missing value for %s\n", argv[i]);
				exit(42);
			}
			++i;
			strcpy(store, argv[i]);
		} else if (0 == strncmp(argv[i], "-d", 2)) {
			printf("Delete: yes\n");
			delete = 1;
		} else {
			fprintf(stderr, "Invalid option: %s\n", argv[i]);
			exit(42);
		}
		i++;
	}

	if (i >= argc) {
		fprintf(stderr, "Missing <old> store depth\n");
		exit(42);
	}
	if (0 == strcmp(argv[i], "mono")) {
		old_depth = -1;
	} else {
		old_depth = atoi(argv[i]);
	}
	i++;
	if (i >= argc) {
		fprintf(stderr, "Missing <new> store depth\n");
		exit(42);
	}
	new_depth = atoi(argv[i]);
	i++;
	if (i < argc) {
		fprintf(stderr, "Ignored addition argumen(s) '%s'\n", argv[i]);
	}

	if (old_depth == -1) {
		switch (new_depth) {
		case 0:
			move_tree_m_0(store);
			break;
		case 1:
			move_tree_m_1(store);
			break;
		case 2:
			move_tree_m_2(store);
			break;
		default:
			fprintf(stderr, "invalid <new depth> %d (possible is 0, 1 or 2)\n",
				new_depth);
		}
	} else if (new_depth == old_depth + 1) {
		switch (old_depth) {
		case 0:
			move_tree_0_1(store, 36);
			break;
		case 1:
			move_tree_1_2(store, 36);
			break;
		case 2:
			move_tree_2_3(store, 36);
			break;
		default:
			fprintf(stderr, "invalid <old depth> %d (possible is 0, 1 or 2)\n",
				old_depth);
		}
	} else if (new_depth == old_depth - 1) {
		switch (old_depth) {
		case 1:
			move_tree_1_0(store);
			break;
		case 2:
			move_tree_2_1(store);
			break;
		case 3:
			move_tree_3_2(store);
			break;
		default:
			fprintf(stderr, "invalid <old depth> %d (possible is 1, 2 or 3)\n",
				old_depth);
		}
	} else {
		fprintf(stderr, "invalid depths: old %d, new %d\n",
			old_depth, new_depth);
		fprintf(stderr, "new must be either old + 1 or old - 1\n");
		exit(43);
	}

	return 0;
}
